package LatentTicTacToe.Data;

import java.util.LinkedList;
import java.util.Stack;

import LatentTicTacToe.misc.Cloner;

public class SimulationBoard {

	private String id;

	private byte[][] situation;
	private byte currentPlayer;
	private LinkedList<Move> availableMoves;
	private Stack<Move> history;

	public SimulationBoard(String id, byte[][] situation, byte currentPlayer) {

		this.id = id;
		this.situation = Cloner.clone(situation);
		this.currentPlayer = currentPlayer;

		history = new Stack<Move>();

		generateAvailableMoves();
	}

	private void generateAvailableMoves() {

		availableMoves = new LinkedList<Move>();

		if (isTerminal() == 0) {
			for (byte i = 0; i < situation.length; i++) {
				for (byte j = 0; j < situation.length; j++) {
					if (situation[j][i] == 0) {
						availableMoves.add(new Move(j, i));
					}
				}
			}
		}
	}

	public void makeMove(Move move) {
		
		if(id.equals("")) {
			id = "0";
			currentPlayer = (byte) (3 - currentPlayer);
			return;
		}

		history.push(move);
		situation[move.x][move.y] = isAfterInvalidMove()?currentPlayer:(byte) (3 - currentPlayer);
		currentPlayer = (byte) (3 - currentPlayer);
		
		addMoveToId(move);

		generateAvailableMoves();
	}

	private void addMoveToId(Move move) {

		StringBuilder builder = new StringBuilder();

		builder.append(id);
		builder.append(".");
		builder.append(move);

		id = builder.toString();
	}

	public void reverseMove() {
		
		if(id.equals("0")) {
			id = "";
			currentPlayer = (byte) (3 - currentPlayer);
			return;
		}

		Move move = history.pop();

		id = id.substring(0, id.length() - 3);
		situation[move.x][move.y] = 0;
		currentPlayer = (byte) (3 - currentPlayer);

		generateAvailableMoves();
	}

	public float[] evaluate() {

		float[] values = new float[availableMoves.size()];

		int index = 0;

		for (Move move : availableMoves) {

			byte[][] tempSituation = Cloner.clone(situation);

			tempSituation[move.x][move.y] = currentPlayer;

			values[index++] = evaluate(tempSituation);
		}

		return values;
	}

	private float evaluate(byte[][] situation) {

		byte[][] count = new byte[4][3];
		float value = 0;

		for (int i = 0; i < situation.length; i++) {
			for (int j = 0; j < situation[0].length; j++) {

				count[0][situation[j][i]]++;
				count[1][situation[i][j]]++;

			}

			count[2][situation[i][i]]++;
			count[3][situation[situation.length - i - 1][i]]++;

			for (int j = 0; j < 2; j++) {

				value += count[j][3 - currentPlayer] == 0 ? count[j][currentPlayer] : count[j][currentPlayer] == 0 ? -count[j][3 - currentPlayer] : 0;

				for (int k = 0; k < 3; k++) {
					count[j][k] = 0;
				}
			}
		}

		for (int j = 2; j < 4; j++) {
			value += count[j][3 - currentPlayer] == 0 ? count[j][currentPlayer] : count[j][currentPlayer] == 0 ? -count[j][3 - currentPlayer] : 0;
		}

		return value / 5;
	}

	public byte isTerminalForMove(Move move) {

		byte[][] tempSituation = Cloner.clone(situation);

		tempSituation[move.x][move.y] = currentPlayer;

		return isTerminal(tempSituation);
	}

	public byte isTerminal() {
		return isTerminal(situation);
	}

	private byte isTerminal(byte[][] situation) {

		for (int i = 0; i < situation.length; i++) {
			for (int j = 0; j < situation[0].length; j++) {
				if (situation[j][i] != 0) {
					if (checkCoordinates(j, i, situation))
						return situation[j][i];
				}
			}
		}

		if (isFull(situation))
			return -1;
		else
			return 0;
	}

	private boolean checkCoordinates(int x, int y, byte[][] situation) {

		byte counter = 1;

		for (int i = -1; i <= 1; i++) {
			for (int j = -1; j <= 1; j++) {

				if (i != 0 || j != 0) {
					for (int k = 1; k <= 3; k++) {

						if (x + j * k >= 0 && x + j * k < situation.length && y + i * k >= 0 && y + i * k < situation[0].length) {
							if (situation[x + j * k][y + i * k] == situation[x][y]) {
								counter++;
							} else {
								break;
							}
						} else {
							break;
						}
					}

					for (int k = 1; k <= 3 - counter; k++) {
						if (x - j * k >= 0 && x - j * k < situation.length && y - i * k >= 0 && y - i * k < situation[0].length) {
							if (situation[x - j * k][y - i * k] == situation[x][y]) {
								counter++;
							} else {
								break;
							}
						} else {
							break;
						}
					}

					if (counter >= 3) {
						return true;
					}
					counter = 1;
				}
			}
		}

		return false;
	}

	private boolean isFull(byte[][] situation) {

		for (int i = 0; i < situation.length; i++) {
			for (int j = 0; j < situation.length; j++) {
				if (situation[j][i] == 0) {
					return false;
				}
			}
		}

		return true;
	}

	public LinkedList<Move> getAvailableMoves() {
		return availableMoves;
	}
	
	public byte getCurrentPlayer() {
		return currentPlayer;
	}
	
	public String getId() {
		return id;
	}
	
	public byte[][] getSituation() {
		return situation;
	}
	
	public boolean isAfterInvalidMove() {
		
		if(id.length() < 6) {
			return false;
		}
		
		return id.substring(id.length() - 6, id.length() - 3).equals(id.substring(id.length() - 3));
	}
	
	public Move removeInvalidMove() {
		
		if(!isAfterInvalidMove()) {
			return null;
		}
		
		Move move = new Move(getXofInvalidMove(), getYofInvalidMove());
		
		id = id.substring(0, id.length() - 6);
		
		return move;
	}
	
	private byte getXofInvalidMove() {
		return Byte.parseByte(id.substring(id.length() - 2, id.length() - 1));
	}
	
	private byte getYofInvalidMove() {
		return Byte.parseByte(id.substring(id.length() - 1));
	}

}
